package main

import (
	"context"
	"fmt"
	"sync"

	"github.com/pion/ice/v3"
	"github.com/pion/stun/v2"
)

const (
	//realm    = "pion.ly"
	//host     = "127.0.0.1"
	//port     = 9003
	//username = "username"
	//password = "password"
	//host     = "stun:websocket.beesoft.ink"
	host     = "turn:websocket.beesoft.ink"
	port     = 3478
	username = "beesoft"
	password = "beesoft"
)

//	func optimisticAuthHandler(string, string, net.Addr) (key []byte, ok bool) {
//		return turn.GenerateAuthKey(username, realm, password), true
//	}
func connect(aAgent, bAgent *ice.Agent) (*ice.Conn, *ice.Conn) {
	gatherAndExchangeCandidates(aAgent, bAgent)

	accepted := make(chan struct{})
	var aConn *ice.Conn

	go func() {
		bUfrag, bPwd, err := bAgent.GetLocalUserCredentials()
		if err != nil {
			panic(err)
		}
		fmt.Printf("bAgent GetLocalUserCredentials bUfrag: %s bPwd: %s\n", bUfrag, bPwd)

		aConn, err = aAgent.Accept(context.TODO(), bUfrag, bPwd)
		if err != nil {
			panic(err)
		}
		fmt.Printf("bAgent Accept\n")

		close(accepted)
	}()

	aUfrag, aPwd, err := aAgent.GetLocalUserCredentials()
	if err != nil {
		panic(err)
	}
	fmt.Printf("aAgent GetLocalUserCredentials aUfrag: %s aPwd: %s\n", aUfrag, aPwd)

	bConn, err := bAgent.Dial(context.TODO(), aUfrag, aPwd)
	if err != nil {
		panic(err)
	}
	fmt.Printf("aAgent Dial\n")

	<-accepted
	return aConn, bConn
}
func gatherAndExchangeCandidates(aAgent, bAgent *ice.Agent) {
	var wg sync.WaitGroup
	wg.Add(2)

	err := aAgent.OnCandidate(func(candidate ice.Candidate) {
		if candidate == nil {
			wg.Done()
		}
	})
	if err != nil {
		panic(err)
	}

	err = aAgent.GatherCandidates()
	if err != nil {
		panic(err)
	}

	err = bAgent.OnCandidate(func(candidate ice.Candidate) {
		if candidate == nil {
			wg.Done()
		}
	})
	if err != nil {
		panic(err)
	}

	err = bAgent.GatherCandidates()
	if err != nil {
		panic(err)
	}

	wg.Wait()

	candidates, err := aAgent.GetLocalCandidates()
	if err != nil {
		panic(err)
	}
	for _, c := range candidates {
		candidateCopy, err := ice.UnmarshalCandidate(c.Marshal())
		if err != nil {
			panic(err)
		}

		err = bAgent.AddRemoteCandidate(candidateCopy)
		if err != nil {
			panic(err)
		}
	}

	candidates, err = bAgent.GetLocalCandidates()
	if err != nil {
		panic(err)
	}
	for _, c := range candidates {
		candidateCopy, err := ice.UnmarshalCandidate(c.Marshal())
		if err != nil {
			panic(err)
		}

		err = aAgent.AddRemoteCandidate(candidateCopy)
		if err != nil {
			panic(err)
		}
	}
}
func main() {
	//listener, err := net.ListenPacket("udp", fmt.Sprintf("%s:%d", host, port))
	//if err != nil {
	//	panic(err)
	//}
	//
	//backend, err := turn.NewServer(turn.ServerConfig{
	//	Realm:       realm,
	//	AuthHandler: optimisticAuthHandler,
	//	PacketConnConfigs: []turn.PacketConnConfig{
	//		{
	//			PacketConn: listener,
	//			RelayAddressGenerator: &turn.RelayAddressGeneratorNone{
	//				Address: host,
	//			},
	//		},
	//	},
	//})
	//if err != nil {
	//	panic(err)
	//}
	//defer func(backend *turn.Server) {
	//	err := backend.Close()
	//	if err != nil {
	//		panic(err)
	//	}
	//}(backend)

	cfg := &ice.AgentConfig{
		NetworkTypes: []ice.NetworkType{
			ice.NetworkTypeUDP4,
			ice.NetworkTypeUDP6,
			ice.NetworkTypeTCP4,
			ice.NetworkTypeTCP6,
		},
		Urls: []*stun.URI{
			{
				Proto:    stun.ProtoTypeUDP,
				Scheme:   stun.SchemeTypeTURN,
				Host:     host,
				Port:     port,
				Username: username,
				Password: password,
			},
		},
		CandidateTypes: []ice.CandidateType{
			ice.CandidateTypeRelay,
		},
	}

	aAgent, err := ice.NewAgent(cfg)
	if err != nil {
		panic(err)
	}
	defer func(agent *ice.Agent) {
		err := agent.Close()
		if err != nil {
			panic(err)
		}
	}(aAgent)
	fmt.Printf("aAgent created\n")

	aConnected := make(chan struct{})
	if err = aAgent.OnConnectionStateChange(func(state ice.ConnectionState) {
		fmt.Printf("aAgent OnConnectionStateChange %s\n", state)
		if state == ice.ConnectionStateConnected {
			close(aConnected)
		}
	}); err != nil {
		panic(err)
	}

	bAgent, err := ice.NewAgent(cfg)
	if err != nil {
		panic(err)
	}
	defer func(agent *ice.Agent) {
		err := agent.Close()
		if err != nil {
			panic(err)
		}
	}(bAgent)
	fmt.Printf("bAgent created\n")

	bConnected := make(chan struct{})
	if err = bAgent.OnConnectionStateChange(func(state ice.ConnectionState) {
		fmt.Printf("bAgent OnConnectionStateChange %s\n", state)
		if state == ice.ConnectionStateConnected {
			close(bConnected)
		}
	}); err != nil {
		panic(err)
	}

	connect(aAgent, bAgent)
	<-aConnected
	<-bConnected
}
